#!/bin/bash
# vim:ts=4:sw=4:noexpandtab
#
# Copyright (c) 2004-2014 Parallels IP Holdings GmbH.
# All rights reserved.
# http://www.parallels.com


export PATH=/bin:/sbin:/usr/bin:/usr/sbin

BUNDLE_DIR="$(cd "`dirname "${0}"`/../.."; pwd)"
BOOTSTRAP="${BUNDLE_DIR}/Contents/MacOS/"

DAEMON_NAME="com.parallels.desktop.launchdaemon"
DAEMON_PLIST_NAME="${DAEMON_NAME}.plist"

INSTALL_DAEMON_PATH="${BUNDLE_DIR}/Contents/Resources/${DAEMON_PLIST_NAME}"
LAUNCHD_PATH="/Library/LaunchDaemons/${DAEMON_PLIST_NAME}"

DISPATCHER_PID_FILE="/var/run/prl_disp_service.pid"

PD_SERVICE_SCRIPT="${BOOTSTRAP}/service"
PD_GUI_APP_NAME="prl_client_app"

LAUNCHD_WRAPPER_SCRIPT_NAME="launchd_wrapper"
LAUNCHD_WRAPPER_SRC_PATH="${BUNDLE_DIR}/Contents/Resources/${LAUNCHD_WRAPPER_SCRIPT_NAME}"
LAUNCHD_WRAPPER_DEST_DIR="/Library/Parallels/Parallels Desktop"
LAUNCHD_WRAPPER_DEST_PATH="${LAUNCHD_WRAPPER_DEST_DIR}/${LAUNCHD_WRAPPER_SCRIPT_NAME}"

OPTION_FORCE="--force"

SLEEP_TIME=2 # in sec
MAX_STOP_TIMEOUT=180 # in sec
MAX_START_TIMEOUT=30 # in sec


#  The logging routine
Log() {
	logger -t 'Parallels:launchd_setup' -p install.info "$@"
	echo "$@"
}

function getLaunchdServicePid() {
	local pid=`launchctl list | grep "${DAEMON_NAME}" | grep -v grep | cut -f 1`
	echo "${pid}"
}

function is_pd_stopped(){
		if [ ! -f "${DISPATCHER_PID_FILE}" ]; then
			return 0
		fi

		return 1
}

function copy_plist(){
	if [ -f "${LAUNCHD_PATH}" ]; then
		Log "Unable to copy file '${LAUNCHD_PATH}'. It already exists."
		return 1
	fi

	cp "${INSTALL_DAEMON_PATH}" "${LAUNCHD_PATH}"
}

function remove_plist(){
	if [ ! -f "${LAUNCHD_PATH}" ]; then
		Log "Unable to remove file '${LAUNCHD_PATH}'. It does not exist."
		return 1
	fi

	rm "${LAUNCHD_PATH}"
}

function wait_for_stop(){
	local curr_wait=0
	while((1)); do
			if is_pd_stopped ;then
				return 0;
			fi

			sleep $SLEEP_TIME

			let "curr_wait=$curr_wait + $SLEEP_TIME"
			if [ "$curr_wait" -gt "$MAX_STOP_TIMEOUT" ]; then
				Log "Unable to stop Parallels Desktop. Timeout expired. Try again later."
				return 1
			fi
			Log "Waiting for Parallels Desktop to stop: ${curr_wait} secs ..."
	done

	return 1
}

function wait_for_start(){
	local curr_wait=0
	while((1)); do
			if ! is_pd_stopped ;then
				return 0;
			fi

			sleep $SLEEP_TIME

			let "curr_wait=$curr_wait + $SLEEP_TIME"
			if [ "$curr_wait" -gt "$MAX_START_TIMEOUT" ]; then
				Log "Unable to start the Parallels Desktop Service. Timeout expired. Try again later."
				return 1
			fi

			Log "Waiting for Parallels Desktop Service to start: ${curr_wait} secs ..."
	done

	return 1
}

function check_and_stop_sba_services(){
		local force_stop="${1}"
		# check if PD is stopped
		if is_pd_stopped ; then
			return 0
		fi

		Log "Parallels Desktop is currently running. It must be stopped."

		if [ "x${force_stop}" != "x${OPTION_FORCE}" ]; then
			return 1
		fi

		Log "Option '${OPTION_FORCE}' is set. Parallels Desktop will be forcibly stopped."

		# try to stop all services by GUI stop
		ps aux | grep "${PD_GUI_APP_NAME}" | grep -qs -v grep
		if [ $? -eq 0 ]; then
			# We will not install any PD services before GUI stop
			# because of GUI will stop their on its shutdown.
			# So we should close it before.
			Log "Parallels Desktop GUI is started. It will be closed."
			killall "${PD_GUI_APP_NAME}"
		else
			"${PD_SERVICE_SCRIPT}" stop
			local res=$?
			if [ $res -ne 0 ] && ! is_pd_stopped ; then
				Log "Unable to stop Parallels Desktop services or agents."
				return 1
			elif [ $res -ne 0 ]; then
				Log "Unable to stop Parallels Desktop agents. Continue."
			fi
		fi

		wait_for_stop
		return $?
}

function is_plist_installed() {
		if [ -f "${LAUNCHD_PATH}" ]; then
			return 0
		fi

		return 1
}

function is_plist_loaded_by_launchd() {
		local pid=`getLaunchdServicePid`
		if [ "x${pid}" == "x" ]; then
			return 1
		fi

		return 0
}

function is_pd_running_by_launchd() {
		local pid=`getLaunchdServicePid`
		if [ "x${pid}" == "x" -o "x${pid}" == "x-" ]; then
			return 1
		fi

		return 0
}

function check_and_remove_launchd_wrapper() {
	if [ ! -f "${LAUNCHD_WRAPPER_DEST_PATH}" ];then
		return 0
	fi

	if ! rm "${LAUNCHD_WRAPPER_DEST_PATH}" ; then
    Log "Unable to delete ${LAUNCHD_WRAPPER_DEST_PATH}"
		return 1
	fi

 	if ! rm -d "${LAUNCHD_WRAPPER_DEST_DIR}" ; then
    Log "Unable to delete ${LAUNCHD_WRAPPER_DEST_DIR}, skip this error."
	fi

	return 0
}

function install_launchd_wrapper() {
	if ! check_and_remove_launchd_wrapper ;then
		return 1
	fi

 	if ! mkdir -p "${LAUNCHD_WRAPPER_DEST_DIR}" ; then
    Log "Unable to create directory ${LAUNCHD_WRAPPER_DEST_DIR}"
		return 1
	fi

	if ! cp "${LAUNCHD_WRAPPER_SRC_PATH}" "${LAUNCHD_WRAPPER_DEST_DIR}/" ; then
    Log "Unable to copy ${LAUNCHD_WRAPPER_SRC_PATH} to ${LAUNCHD_WRAPPER_DEST_DIR}"
		return 1
	fi

	return 0
}

function install() {
		local force_stop="${1}"
    Log "Installing daemon ${DAEMON_NAME} ..."

		# check if it is already installed
		if is_plist_loaded_by_launchd ;then
			Log "The daemon is already installed."
			if [ "x${force_stop}" != "x${OPTION_FORCE}" ]; then
				return 1
			fi

			Log "Option '${OPTION_FORCE}' is set. The daemon will be uninstalled before continuing."

			uninstall
		fi

		if is_plist_loaded_by_launchd ;then
			Log "Unable to uninstall the daemon before installation. "
			return 1
		fi

 		check_and_stop_sba_services "${1}"
		if [ $? -ne 0 ];then
			Log "Unable to stop Parallels Desktop. Try again later."
			return 1
		fi

		if ! install_launchd_wrapper ;then
			Log "Unable to install launchd_wrapper."
			return 1
		fi

		# copy launchd.plist to /Library/LaunchDaemons
		# load && start it
		cp "${INSTALL_DAEMON_PATH}" "${LAUNCHD_PATH}" && launchctl load "${LAUNCHD_PATH}"
		if [ $? -ne 0 ]; then
			Log "Unable to install the daemon."
			return 1
		fi

		if ! wait_for_start ;then
			Log "Unable to wait to start the daemon."
			return 1
		fi

		Log "Daemon '${DAEMON_NAME}' was successfully installed and started."
    return 0
}

function uninstall() {

    Log "Uninstalling daemon ${DAEMON_NAME} ..."

		# check if it is already installed
		if is_plist_loaded_by_launchd ; then
			launchctl stop "${DAEMON_NAME}"
		elif is_plist_installed ; then
			remove_plist
			return $?
		else
			Log "The daemon is not installed."
			return 1
		fi

		local curr_wait=0
		while((1))
		do
			local pid=`getLaunchdServicePid`

			if [ "x${pid}" == "x" -o "x${pid}" == "x-" ]; then
				Log "The daemon is stopped."
				break
			fi

			sleep $SLEEP_TIME

			let "curr_wait=$curr_wait + $SLEEP_TIME"
			if [ "$curr_wait" -gt "$MAX_STOP_TIMEOUT" ]; then
				Log "Unable to stop the daemon. Timeout expired."
				return 1
			fi

			Log "Waiting for daemon ${DAEMON_NAME} to stop: ${curr_wait} secs ..."

		done

		if ! is_plist_installed ; then
			# We need an existing plist to properly unload service by 'launchctl unload'
			copy_plist
		fi

		launchctl unload "${LAUNCHD_PATH}"
		if [ $? -ne 0 ]; then
			Log "Unable to unload the daemon plist '${LAUNCHD_PATH}'"
			return 1
		fi

		if is_plist_installed && ! remove_plist ; then
			return 1
		fi

		if ! check_and_remove_launchd_wrapper ;then
			Log "Unable to remove launchd_wrapper."
			return 1
		fi

		Log "Daemon '${DAEMON_NAME}' was successfully uninstalled."
    return 0
}

function need_to_start_as_service() {
	is_plist_installed
}

function is_service_started() {
	is_plist_installed && is_plist_loaded_by_launchd && is_pd_running_by_launchd
}

function start_service() {
	launchctl start "${DAEMON_NAME}"
}

function need_to_install(){
	if ! is_plist_installed ; then
		return 1
	fi

	if ! is_plist_loaded_by_launchd ; then
		return 0
	fi

	return 1
}

function need_to_uninstall(){
	if is_plist_installed ; then
		return 1
	fi

	if is_plist_loaded_by_launchd ; then
		return 0
	fi

	return 1
}


# check for root
if [ "`id -u`" != "0" ]; then
	Log "This script must be executed by the root user only."
	exit 1
fi

RC=1
case $1 in
    install)
        install "${2}"
				RC=$?
    ;;
    uninstall)
        uninstall
				RC=$?
    ;;
		copy_plist)
				copy_plist
				RC=$?
		;;
		remove_plist)
				remove_plist
				RC=$?
		;;
		need_to_install)
				need_to_install
				RC=$?
		;;
		need_to_uninstall)
				need_to_uninstall
				RC=$?
		;;
 		need_to_start_as_service)
				need_to_start_as_service
				RC=$?
		;;
 		is_service_started)
				is_service_started
				RC=$?
		;;
 		start_service)
				start_service
				RC=$?
		;;
   * )
    echo "Usage: launchd_setup install [ ${OPTION_FORCE}] | uninstall | copy_plist | remove_plist"
		echo       "| need_to_install | need_to_uninstall | need_to_start_as_service |is_service_started | start_service "
		RC=1
    ;;
esac

exit "${RC}"

